﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using ScheduleParserWorkerRole;

namespace RecorderWorkerRole
{
    public class Downloader
    {
        private WebClient _client;



        //private DateTime GetShowStopTime(schedule iCurrentShow)
        //{
        //    //There is a case, when there is a gap in the broadcast plan, like
        //    //1:00-1:30 Pisničky
        //    //2:00-2:30 Zprávy
        //    //For this case, we need to change first stop time to the second's start time
        //    //However, sometimes, there are a sub-plans
        //    //11:00-14:00 Polední rádiožurnál
        //    //11:00-12:00 Předpolední část
        //    //12:00-14:00 Popolední část
        //    //In this case, the first stop time should not be shifted to the second's one
        //    var aShowStopTime = iCurrentShow.StopTime;
        //    foreach (var aNextShow in _schedules.Where(show => show.StartTime > DateTime.UtcNow)
        //        .Where(show => show.stationId == iCurrentShow.stationId)
        //        .OrderBy(show => show.StartTime)
        //        .Take(1))
        //    {
        //        if (aShowStopTime < aNextShow.StartTime)
        //        {
        //            aShowStopTime = aNextShow.StartTime;
        //        }
        //    }
        //    return aShowStopTime;
        //}

        public enum EDownloadStates
        {
            PendingDownload,
            ConnectionRequested,
            DownloadStarted,
            DownloadCompleted,
            DownloadRegistered,
            DownloadError,
            DownloadFinishedForgetMe
        };
        public EDownloadStates State;
        private DateTime DownloadAttemptTimestamp { get; set; }
        public string BlobUri { get; set; }
        public DateTime StateExpirationTime = DateTime.UtcNow;
        public DownloadDetails CurrentDownloadInfo;//= new DownloadDetails() { HumanReadableName = "uninitialized" };

        public Downloader(schedule iCurrentShow)
        {
            State = EDownloadStates.PendingDownload;
            CurrentDownloadInfo = new DownloadDetails(iCurrentShow);
        }


        public void ConnectionRequestTimeoutAction()
        {
            if (DateTime.UtcNow < DownloadAttemptTimestamp.AddSeconds(5))
            {
                return;
            }
            if (CurrentDownloadInfo.StartTime > DateTime.UtcNow)
            {
                Trace.TraceInformation("RETRY: {0}, {1}", CurrentDownloadInfo, CustomDatesFormatter.GetPrintable(DateTime.UtcNow));
                _client.CancelAsync();
                Dispose();
                State = EDownloadStates.PendingDownload;
                RecorderWorkerRole.MainCycleSynchronization.Set();
            }
            else
            {
                Trace.TraceInformation("GIVEUP: {0}, {1}", CurrentDownloadInfo, CustomDatesFormatter.GetPrintable(DateTime.UtcNow));
                State = EDownloadStates.DownloadError;
            }
        }

        public void Dispose()
        {
            _client.Dispose();
            _client = null;
        }


        public void Start(StationsDataModelDataContext context)
        {
            StartShowDownload();
            Trace.TraceInformation("PREP: {0}, {1}", CurrentDownloadInfo, CustomDatesFormatter.GetPrintable(DateTime.UtcNow));
            State = EDownloadStates.ConnectionRequested;
            StateExpirationTime = DateTime.UtcNow.AddSeconds(5);
            DownloadAttemptTimestamp = DateTime.UtcNow;
        }

        private void StartShowDownload()
        {
            CurrentDownloadInfo.SetStreamForDownload(CurrentDownloadInfo.Streams.FirstOrDefault());
            StartStreamDownloadAsync();
        }

        private void StartStreamDownloadAsync()
        {
            _client = new WebClient();
            _client.OpenReadCompleted += StartStreamDownladCallback;
            _client.OpenReadAsync(new Uri(CurrentDownloadInfo.StreamUri, UriKind.Absolute), CurrentDownloadInfo);
        }



        private void StartStreamDownladCallback(object iSenderWebClient, OpenReadCompletedEventArgs iArguments)
        {
            var aCurrentDownload = iArguments.UserState as DownloadDetails;
            if (aCurrentDownload != null)
            {
                DownloadStreamToNewBlob(iArguments, aCurrentDownload);
            }
            else
            {
                SignalStateChange(EDownloadStates.DownloadError);
            }
        }

        private void DownloadStreamToNewBlob(OpenReadCompletedEventArgs iArguments, DownloadDetails aCurrentDownload)
        {
            try
            {
                DownloadStream(aCurrentDownload, iArguments.Result);
                BlobUri = RecordingsBlobStorage.UploadToBlob(aCurrentDownload);
                SignalStateChange(EDownloadStates.DownloadCompleted);
            }
            catch (Exception exc)
            {
                SignalStateChange(EDownloadStates.DownloadError);
                Trace.TraceInformation("FAILed to download {0}  :  {1}  : {2}",
                    aCurrentDownload, aCurrentDownload.StreamUri, exc);
            }
        }

        private int DownloadStream(DownloadDetails aCurrentDownload, Stream iStream)
        {
            using (iStream)
            {
                State = EDownloadStates.DownloadStarted;
                Trace.TraceInformation("START: {0}, {1}", aCurrentDownload, CustomDatesFormatter.GetPrintable(DateTime.UtcNow));
                aCurrentDownload.DownloadedBytes = new byte[aCurrentDownload.Length * 2];
                int progress = 0;
                int read;
                while (
                    (read =
                        iStream.Read(aCurrentDownload.DownloadedBytes, progress,
                            Math.Min(327680, aCurrentDownload.DownloadedBytes.Length - progress))) > 0
                    && progress < aCurrentDownload.DownloadedBytes.Length
                    && DateTime.UtcNow < CurrentDownloadInfo.StopTime.AddSeconds(15))
                {
                    progress += read;
                    //var before = ((progress - read) * 10) / aCurrentDownload.Length;
                    //var after = ((progress) * 10) / aCurrentDownload.Length;
                    //if (before != after)
                    //{
                    //    Trace.TraceInformation("Progress: {0}, {1:N0}, {2}", aCurrentDownload, progress, GetPrintable(DateTime.UtcNow));
                    //}
                }
                aCurrentDownload.Length = progress;
                return progress;
            }
        }

        private void SignalStateChange(EDownloadStates iNewState)
        {
            try
            {
                State = iNewState;
                RecorderWorkerRole.MainCycleSynchronization.Set();
            }
            catch (Exception e)
            {
                Trace.TraceInformation("Failed to signal state change: {0}, {1}", CustomDatesFormatter.GetPrintable(DateTime.UtcNow), e);
            }
        }

        public void Cleanup()
        {
            _client = null;
            State = EDownloadStates.DownloadFinishedForgetMe;
            StateExpirationTime = DateTime.UtcNow.AddYears(10);
            CurrentDownloadInfo = null;
        }

        public void UpdateStateExpirationTime()
        {
            if (CurrentDownloadInfo != null)
            {
                StateExpirationTime = CurrentDownloadInfo.StopTime.AddSeconds(15);
            }
        }
    }
}